using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Data.Services.Client;
using System.Linq;
using Caliburn.Micro;
using MediaOwl.Netflix.NetflixServiceReference;

namespace MediaOwl.Netflix.Data
{
    [Export(typeof(INetflixDataRepository))]
    public class NetflixDataRepository : INetflixDataRepository
    {
        #region Fields

        private readonly NetflixCatalog catalog;

        #endregion

        #region Constructor

        public NetflixDataRepository()
        {
            catalog = new NetflixCatalog(new Uri(NetflixDataAccess.BaseUri))
                          {MergeOption = MergeOption.OverwriteChanges};
        }

        #endregion

        #region Methods

        public IResult GetTitles(string searchTerm, Action<IEnumerable<Title>, long> onSuccess, Action<Exception> onFail = null, int page = 1, int pageSize = 0)
        {
            if (pageSize < 1)
                pageSize = int.Parse(NetflixDataAccess.DefaultPageSize);

            if (page < 1)
                page = 1;

            var query = (from title in catalog.Titles.IncludeTotalCount()
                         where title.Name.Contains(searchTerm) ||
                                title.ShortName.Contains(searchTerm)
                         orderby title.ReleaseYear descending
                         select new Title
                                    {
                                        Id = title.Id,
                                        BoxArt = title.BoxArt,
                                        Type = title.Type,
                                        ReleaseYear = title.ReleaseYear,
                                        ShortName = title.ShortName
                                    })
                         .Skip((page - 1) * pageSize).Take(pageSize);

            return new LoadDataResult<Title>(catalog, query, onSuccess, onFail);
        }

        public IResult GetTitle(string id, Action<Title> onSuccess, Action<Exception> onFail = null)
        {
            var query = from title in catalog.Titles
                             .Expand(t => t.Awards)
                             .Expand(t => t.Genres)
                             .Expand(t => t.Directors)
                             .Expand(t => t.Languages)
                             .Expand(t => t.Cast)
                        where title.Id == id
                        select title;

            return new LoadEntityResult<Title>(catalog, query, onSuccess, onFail);
        }

        public IResult GetLatestTitles(Action<IEnumerable<Title>, long> onSuccess, Action<Exception> onFail = null, int page = 1, int pageSize = 0)
        {
            if (pageSize < 1)
                pageSize = int.Parse(NetflixDataAccess.DefaultPageSize);

            if (page < 1)
                page = 1;

            var query = (from title in catalog.Titles.IncludeTotalCount()
                         orderby title.ReleaseYear descending
                         select new Title { Name = title.Name, BoxArt = title.BoxArt, Type = title.Type, ReleaseYear = title.ReleaseYear })
                         .Skip((page - 1) * pageSize).Take(pageSize);

            return new LoadDataResult<Title>(catalog, query, onSuccess, onFail);
        }

        public IResult GetPeople(Action<IEnumerable<Person>, long> onSuccess, Action<Exception> onFail = null, int page = 1, int pageSize = 0)
        {
            if (pageSize < 1)
                pageSize = int.Parse(NetflixDataAccess.DefaultPageSize);

            if (page < 1)
                page = 1;

            var query = (from person in catalog.People.IncludeTotalCount()
                         orderby person.Name 
                         select person)
                         .Skip((page - 1) * pageSize).Take(pageSize);

            return new LoadDataResult<Person>(catalog, query, onSuccess, onFail);
        }

        public IResult GetPeople(string searchTerm, Action<IEnumerable<Person>, long> onSuccess, Action<Exception> onFail = null, int page = 1, int pageSize = 0)
        {
            if (pageSize < 1)
                pageSize = int.Parse(NetflixDataAccess.DefaultPageSize);

            if (page < 1)
                page = 1;

            var query = (from person in catalog.People.IncludeTotalCount()
                         where person.Name.Contains(searchTerm)
                         orderby person.Name
                         select person)
                         .Skip((page - 1) * pageSize).Take(pageSize);

            return new LoadDataResult<Person>(catalog, query, onSuccess, onFail);
        }

        public IResult GetPerson(int id, Action<Person> onSuccess, Action<Exception> onFail = null)
        {
            var query = from person in catalog.People
                             .Expand(x =>x.Awards)
                             .Expand(x => x.TitlesActedIn.SubExpand(m => m.Genres))
                             .Expand(x => x.TitlesDirected.SubExpand(m => m.Genres))
                        where person.Id == id
                        select person;

            return new LoadEntityResult<Person>(catalog, query, onSuccess, onFail);
        }

        public IResult GetGenres(Action<IEnumerable<Genre>, long> onSuccess, Action<Exception> onFail = null, int page = 1, int pageSize = 0)
        {
            if (pageSize < 1)
                pageSize = int.Parse(NetflixDataAccess.DefaultPageSize);

            if (page < 1)
                page = 1;

            var query = (from genre in catalog.Genres.IncludeTotalCount()
                         orderby genre.Name descending
                         select genre)
                         .Skip((page - 1) * pageSize).Take(pageSize);

            return new LoadDataResult<Genre>(catalog, query, onSuccess, onFail);
        }

        public IResult GetGenres(string searchTerm, Action<IEnumerable<Genre>, long> onSuccess, Action<Exception> onFail = null, int page = 1, int pageSize = 0)
        {
            if (pageSize < 1)
                pageSize = int.Parse(NetflixDataAccess.DefaultPageSize);

            if (page < 1)
                page = 1;

            var query = (from genre in catalog.Genres.IncludeTotalCount()
                         where genre.Name.Contains(searchTerm)
                         orderby genre.Name
                         select genre)
                         .Skip((page - 1) * pageSize).Take(pageSize);

            return new LoadDataResult<Genre>(catalog, query, onSuccess, onFail);
        }

        public IResult GetGenre(string id, Action<Genre> onSuccess, Action<Exception> onFail = null)
        {
            var query = from genre in catalog.Genres
                             .Expand(x => x.Titles)
                        where genre.Name == id
                        select genre;

            return new LoadEntityResult<Genre>(catalog, query, onSuccess, onFail);
        }

        public IResult GetBestTitlesByGenre(string genreId, Action<IEnumerable<Title>, long> onSuccess, Action<Exception> onFail = null, int page = 1, int pageSize = 0)
        {
            if (pageSize < 1)
                pageSize = int.Parse(NetflixDataAccess.DefaultPageSize);

            if (page < 1)
                page = 1;

            var query = (from g in catalog.Genres.IncludeTotalCount()
                             from title in g.Titles
                             where g.Name == genreId
                             orderby title.AverageRating descending
                             select title).Skip((page - 1) * pageSize).Take(pageSize);

            return new LoadDataResult<Title>(catalog, query, onSuccess, onFail);
        }

        public IResult GetLatestTitlesByGenre(string genreId,  Action<IEnumerable<Title>, long> onSuccess, Action<Exception> onFail = null, int page = 1, int pageSize = 0)
        {
            if (pageSize < 1)
                pageSize = int.Parse(NetflixDataAccess.DefaultPageSize);

            if (page < 1)
                page = 1;

            var query = (from g in catalog.Genres.IncludeTotalCount()
                         from title in g.Titles
                         where g.Name == genreId
                         orderby title.ReleaseYear descending
                         select title).Skip((page - 1) * pageSize).Take(pageSize);

            return new LoadDataResult<Title>(catalog, query, onSuccess, onFail);
        }

        #endregion
    }

    public interface INetflixDataRepository
    {
        IResult GetLatestTitles(Action<IEnumerable<Title>, long> onSuccess, Action<Exception> onFail = null, int page = 1,
                          int pageSize = 20);

        IResult GetTitles(string searchTerm, Action<IEnumerable<Title>, long> onSuccess, Action<Exception> onFail = null,
                          int page = 1, int pageSize = 20);

        IResult GetTitle(string id, Action<Title> onSuccess, Action<Exception> onFail = null);

        IResult GetPeople(Action<IEnumerable<Person>, long> onSuccess, Action<Exception> onFail = null, int page = 1,
                          int pageSize = 0);

        IResult GetPeople(string searchTerm, Action<IEnumerable<Person>, long> onSuccess,
                          Action<Exception> onFail = null, int page = 1, int pageSize = 0);

        IResult GetPerson(int id, Action<Person> onSuccess, Action<Exception> onFail = null);

        IResult GetGenres(Action<IEnumerable<Genre>, long> onSuccess, Action<Exception> onFail = null, int page = 1,
                          int pageSize = 0);

        IResult GetGenres(string searchTerm, Action<IEnumerable<Genre>, long> onSuccess, Action<Exception> onFail = null,
                          int page = 1, int pageSize = 0);

        IResult GetGenre(string id, Action<Genre> onSuccess, Action<Exception> onFail = null);

        IResult GetBestTitlesByGenre(string genreId, Action<IEnumerable<Title>, long> onSuccess,
                                     Action<Exception> onFail = null, int page = 1, int pageSize = 0);

        IResult GetLatestTitlesByGenre(string genreId, Action<IEnumerable<Title>, long> onSuccess,
                                       Action<Exception> onFail = null, int page = 1, int pageSize = 0);
    }
}